from weakref import WeakKeyDictionary

# all the events

class Event():
    '''
    superclass for events
    '''
    name = 'Default Event'
    client_number = None
    send_over_network = False

class TickEvent(Event):
    ''' update the game! gets created 60 times a sec '''
    def __init__(self, delta_time):
        self.name = 'Tick Event'
        self.delta_time = delta_time

class NewClientConnectedEvent(Event):
    '''
    used by the server to know when a new
    client connects
    '''
    def __init__(self, client_number, client_ip):
        self.name = 'New Client Connected Event'
        self.client_number = client_number
        self.client_ip = client_ip

class ConnectedToServerEvent(Event):
    '''used by the client to know when weve connected'''
    def __init__(self):
        self.name = 'Connected To Server Event'

class StopNetworkConnectionEvent(Event):
    '''used by the client state to inform everyone that were stopping'''
    def __init__(self):
        self.name = 'Stop Network Connection Event'

class ConnectionFailedEvent(Event):
    '''used by the client to know when we disconnected'''
    def __init__(self, reconnectable_factory):
        self.name = 'Connection Failed Event'
        self.reconnectable_factory = reconnectable_factory

class ConnectionLostEvent(Event):
    '''used by the client when we lose connection of the server'''
    def __init__(self):
        self.name = 'Connection Lost Event'

class UserQuitEvent(Event):
    '''when our clients quit the game'''
    def __init__(self):
        self.name = 'User Quit Event'

class TextMessageEvent(Event):
    ''' Just text '''
    def __init__(self, text, send_over_network):
        self.name = 'Text Message Event'
        self.text = text # any string
        self.send_over_network = send_over_network # True or False

class CompleteGameStateEvent(Event):
    '''
    Holds the  visual info for every
    object in the game
    '''
    # example of game state list
    # [['OBJECT NAME', id, [position]], object2, object3...
    # [['CHARACTER', 19376408, [300, 300]], ect... ]
    def __init__(self, game_state):
        self.name = 'Complete Game State Event'
        self.game_state = game_state
        self.send_over_network = True

class CompleteGameStateEvent(Event):
    '''Holds the info for every object'''
    def __init__(self, complete_game_state):
        self.name = 'Complete Game State Event'
        self.complete_game_state = complete_game_state

class ChangedGameStateEvent(Event):
    '''Contains the state for all the objects which have changed'''
    def __init__(self, changed_game_state):
        self.name = 'Changed Game State Event'
        self.changed_game_state = changed_game_state

class CompleteGameStateRequestEvent(Event):
    def __init__(self):
        self.name = 'Complete Game State Request Event'
        self.send_over_network = True

class ChangedGameStateRequestEvent(Event):
    def __init__(self):
        self.name = 'Changed Game State Request Event'
        self.send_over_network = True

##### USER INPUT EVENTS #####
class UserMouseInputEvent(Event):
    '''
    when the user clicks down on the mouse
    '''
    def __init__(self, mouse_button, mouse_position):
        self.name = 'User Mouse Input Event'
        self.mouse_button = mouse_button
        self.mouse_position = mouse_position

class UserKeyboardInputEvent(Event):
    '''
    When the user presses keyboard keys
    generated by the KeyboardController
    '''
    def __init__(self, keyboard_input, client_number = None):  
        self.name = 'User Keyboard Input Event'
        self.keyboard_input = keyboard_input
        self.client_number = client_number # server uses this to track which client's input it was
        self.send_over_network = True

class PlaceWallRequestEvent(Event):
    '''
    when the user clicks and wants to place a new wall
    '''
    def __init__(self, grid_position, client_number = None):
        self.name = 'Place Wall Request Event'
        self.grid_position = grid_position
        self.client_number = client_number # server uses this to track which client's input it was
        self.send_over_network = True

class ShootProjectileRequestEvent(Event):
    '''this is created when the user clicks and wants to shoot a projectile'''
    def __init__(self, destination_position, client_number=None):
        self.name = 'Shoot Projectile Request Event'
        self.destination_position = destination_position
        self.client_number = client_number
        self.send_over_network = True

class AddEnemyToGameRequestEvent(Event):
    '''
    EnemyGenerator creates this to make a new enemy for the server state
    not sent over the network
    '''
    def __init__(self, spawn_position):
        self.name = 'Add Enemy To Game Request Event'
        self.spawn_position = spawn_position
        
class EventEncoder():
    def __init__(self):
        pass

    def encode_event(self, event):
        if event.name == 'Text Message Event':
            encoded_event = []
            dict_event = {'name': 'Text Message Event',
                          'text': event.text}
            event_list.append(dict_event)
            return encoded_event
        
        elif event.name == 'User Keyboard Input Event':
            encoded_event = []
            dict_event = {'name': 'User Keyboard Input Event',
                          'input': event.keyboard_input}
            encoded_event.append(dict_event)
            return encoded_event

        elif event.name == 'Place Wall Request Event':
            encoded_event = []
            dict_event = {'name': 'Place Wall Request Event',
                          'grid_position': event.grid_position}
            encoded_event.append(dict_event)
            return encoded_event

        elif event.name == 'Shoot Projectile Request Event':
            encoded_event = []
            dict_event = {'name': 'Shoot Projectile Request Event',
                          'destination position': event.destination_position}
            encoded_event.append(dict_event)
            return encoded_event

        else:
            raise RuntimeError('The event <' + event.name + '> cannot be encoded by the EventEncoder!')
        
    def decode_event(self, encoded_event, client_number = None):
        '''
        event: [{'input': 'LEFT', 'name': 'User Keyboard Input Event'}]
        returns: Event instance
        '''
        for e in encoded_event:
            if e['name'] == 'User Keyboard Input Event':
                event = UserKeyboardInputEvent(e['input'], client_number)
                return event
            elif e['name'] == 'Place Wall Request Event':
                event = PlaceWallRequestEvent(e['grid_position'], client_number)
                return event
            elif e['name'] == 'Shoot Projectile Request Event':
                event = ShootProjectileRequestEvent(e['destination position'], client_number)
                return event


class EventManager():
    '''
    acts as the connection between all the different program elements,
    all events are sent through here.
    '''
    def __init__(self):
        self.listeners = WeakKeyDictionary() # holds our listeners
        self.event_queue = []
        self.processing_events = True

    def add_listener(self, listener):
        self.listeners[listener] = True

    def remove_listener(self, listener):
        if listener in self.listeners:
            del self.listeners[listener]

    def post(self, event):
        self._add_event_to_queue(event)
        if event.name == 'Tick Event':
            self._process_event_queue()
        if event.name == 'Program Quit Event':
            self.processing_events = False

    def _add_event_to_queue(self, event):
        self.event_queue.append(event)

    def _process_event_queue(self):
        if self.processing_events:
            event_number = 0
            while event_number < len(self.event_queue):
                event = self.event_queue[event_number]
                event_number += 1
                for listener in self.listeners:
                    listener.notify(event)
            self.event_queue = []


